<?php
/**
 * jishupu-cms
 * @Author:技术铺
 * @Date:2021/11/25
 */

namespace App\Services;

use App\Models\Attachment;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Intervention\Image\Facades\Image;
use Symfony\Component\HttpFoundation\File\Exception\FileException;

/**
 * 附件服务层：上传、存储、下载等服务
 * Class AttachmentService
 * @package App\Services
 */
class AttachmentService
{

    /**
     * 上传表单元素名称，默认file
     * @var string
     */
    private $formName = 'file';

    /**
     * 新附件名 注：批量上传失效
     * @var string
     */
    private $newFileName = '';

    /**
     *上传目录
     * @var string
     */
    private $uploadDir = '';

    /**
     * 上传文件类型，选项：image（图片）、media（音视频），默认综合，自检文件类型
     * @var string
     */
    private $uploadType = '';

    /**
     * 允许上传附件类型
     * @var array
     */
    private $allowTypeList = [];

    /**
     * 默认附件上传大小（KIB）,默认0不限制
     * @var int
     */
    private $allowSize = 0;

    /**
     * 允许上传图片类型
     * @var array
     */
    private $allowImageTypeList = [];

    /**
     * 默认图片上传大小（KIB）,默认0不限制
     * @var int
     */
    private $allowImageSize = 0;

    /**
     * 允许上传音视频类型
     * @var array
     */
    private $allowMediaTypeList = [];

    /**
     * 默认音视频上传大小（KIB）,默认0不限制
     * @var int
     */
    private $allowMediaSize = 0;


    /**
     * 允许上传其他附件类型
     * @var array
     */
    private $allowOthersTypeList = [];

    /**
     * 默认其他附件上传大小（KIB）,默认0不限制
     * @var int
     */
    private $allowOthersSize = 0;

    /**
     * 图片类型字典
     * @var array
     */
    private $imageTypeDictionary = [];

    /**
     * 音视频类型字典
     * @var array
     */
    private $mediaTypeDictionary = [];

    /**
     * 其他附件类型字典
     * @var array
     */
    private $othersTypeDictionary = [];

    /**
     * 请求
     * @var
     */
    private $request;


    /**
     * 是否多图上传
     * @var
     */
    private $multiple = false;


    /**
     * 多附件上传时：每次上传数量限制
     * @var int
     */
    private $perLimit = 5;


    /**
     * 是否开启图片水印
     * @var int
     */
    private $isWatermark = 0;


    /**
     * 水印图片路径
     * @var string
     */
    private $watermarkImage = "";

    /**
     * 满足图片打水印宽度
     * @var int
     */
    private $watermarkConditionWidth = 0;

    /**
     * 满足图片打水印高度
     * @var int
     */
    private $watermarkConditionHeight = 0;

    /**
     * 图片水印透明度
     * @var int
     */
    private $watermarkPct = 0;

    /**
     * 图片水印位置
     * @var int
     */
    private $watermarkPosition = 0;


    /**
     * AttachmentService constructor.
     */
    public function __construct()
    {
        // 附件字典：用于判断附件扩展名范围
        $this->imageTypeDictionary = DictionaryService::getItemsValuesArrayGroupByDesc('attachmentTypeVar', 'image');
        $this->mediaTypeDictionary = DictionaryService::getItemsValuesArrayGroupByDesc('attachmentTypeVar', 'media');
        $this->othersTypeDictionary = DictionaryService::getItemsValuesArrayGroupByDesc('attachmentTypeVar', 'others');

        // 附件配置
        $attachmentConfig = ConfigService::getConfigItemValueList('attachment');

        // 默认附件设置
        $this->allowTypeList = filled($attachmentConfig['attachment_upload_ext']) ? explode(",", $attachmentConfig['attachment_upload_ext']) : [];
        $this->allowSize = $attachmentConfig['attachment_upload_maxsize'] ?? 0;

        // 图片设置
        $this->allowImageTypeList = $this->getAllowImageTypeList();
        $this->allowImageSize = $attachmentConfig['image_upload_maxsize'] ?? 0;
        $this->isWatermark = $attachmentConfig['is_watermark'] ?? 0;
        $this->watermarkImage = $attachmentConfig['watermark_image'] ?? "";
        $this->watermarkConditionWidth = $attachmentConfig['watermark_condition_width'] ?? 0;
        $this->watermarkConditionHeight = $attachmentConfig['watermark_condition_height'] ?? 0;
        $this->watermarkPct = $attachmentConfig['watermark_pct'] ?? 0;
        $this->watermarkPosition = $attachmentConfig['watermark_position'] ?? 0;

        // 音视频设置
        $this->allowMediaTypeList = $this->getAllowMediaTypeList();
        $this->allowMediaSize = $attachmentConfig['media_upload_maxsize'];

        // 其他附件
        $this->allowOthersTypeList = $this->getAllowOthersTypeList();
//        $this->allowOthersSize = 0;

        //
        $this->uploadDir = Carbon::now()->format('Y/m/d');
        $this->request = Request();
    }

    /**
     * 获取允许上传附件类型
     * @return array|string[]
     */
    public function getAllowTypeList()
    {
        return $this->allowTypeList;
    }

    /**
     * 获取允许上传大小
     * @return int
     */
    public function getAllowSize()
    {
        $getMaxFilesize = UploadedFile::getMaxFilesize() / 1024 / 1048;
        return $this->allowSize > $getMaxFilesize || $this->allowSize == 0 ? $getMaxFilesize : $this->allowSize;
    }

    /**
     * 获取允许上传图片附件类型
     * @return array|string[]
     */
    public function getAllowImageTypeList()
    {
        return collect($this->allowTypeList)->filter(function ($value, $key) {
            return in_array($value, $this->imageTypeDictionary);
        })->values()->all();
    }

    /**
     * 获取允许上传图片附件大小
     * @return int
     */
    public function getAllowImageSize()
    {
        return $this->allowImageSize > 0 ? $this->allowImageSize : $this->getAllowSize();
    }

    /**
     * 获取允许上传音视频附件类型
     * @return array|string[]
     */
    public function getAllowMediaTypeList()
    {
        return collect($this->allowTypeList)->filter(function ($value, $key) {
            return in_array($value, $this->mediaTypeDictionary);
        })->values()->all();
    }

    /**
     * 获取允许上传音视频附件大小
     * @return int
     */
    public function getAllowMediaSize()
    {
        return $this->allowMediaSize > 0 ? $this->allowMediaSize : $this->getAllowSize();
    }


    /**
     * 获取允许其他附件类型
     * @return array|string[]
     */
    public function getAllowOthersTypeList()
    {
        return collect($this->allowTypeList)->filter(function ($value, $key) {
            return in_array($value, $this->othersTypeDictionary);
        })->values()->all();
    }

    /**
     * 获取允许上传其他附件大小
     * @return int
     */
    public function getAllowOthersSize()
    {
        return $this->allowOthersSize > 0 ? $this->allowOthersSize : $this->getAllowSize();
    }

    /**
     * 获取设置上传表单元素名称
     * @param string $name
     * @return mixed
     */
    public function setFormName($name = "")
    {
        $this->formName = $name;
        return $this;
    }

    /**
     * 设置附件名
     * 说明：批量上传时设置无效
     * @param string $name
     * @return mixed
     */
    public function setFileName($name = "")
    {
        $this->newFileName = $name;
        return $this;
    }

    /**
     * 设置附件上传目录，默认”年/月/日“存储目录结构
     * @param string $dir
     * @return mixed
     */
    public function setUploadDir($dir = "")
    {
        $this->uploadDir = $dir ? trim($dir, '/') . "/" . $this->uploadDir : $this->uploadDir;
        if (!Storage::makeDirectory($this->uploadDir)) {
            throw new FileException(__('attachment.error_cant_write'));
        }
        return $this;
    }

    /**
     * 上传文件类型、大小等校验
     * @param $file
     * @return bool
     * @throws ValidationException
     */
    private function validateFile($file)
    {
        $config = [
            'default' => [
                'allowTypeList' => $this->allowTypeList,
                'allowSize' => $this->getAllowSize()
            ],
            'image' => [
                'allowTypeList' => $this->allowImageTypeList,
                'allowSize' => $this->getAllowImageSize()
            ],
            'media' => [
                'allowTypeList' => $this->allowMediaTypeList,
                'allowSize' => $this->getAllowMediaSize()
            ],
            'others' => [
                'allowTypeList' => $this->allowOthersTypeList,
                'allowSize' => $this->getAllowOthersSize()
            ]
        ];

        if ($this->uploadType) {
            $getConfig = $config[$this->uploadType];
        } else {
            $extension = $file->extension();
            if (in_array($extension, $this->imageTypeDictionary)) {
                $uploadType = "image";
            } else if (in_array($extension, $this->mediaTypeDictionary)) {
                $uploadType = "media";
            } else if (in_array($extension, $this->othersTypeDictionary)) {
                $uploadType = "others";
            } else {
                $uploadType = "default";
            }
            $getConfig = $config[$uploadType];
        }
        // 校验
        $allowTypeList = $getConfig['allowTypeList'];
        $allowSize = $getConfig['allowSize'];
        if (empty($allowTypeList)) {
            if ($this->multiple) return false;
            throw new FileException(__('attachment.error_extension', ['extension' => '无']));
        }
        $allowTypeListString = join(',', $allowTypeList);
        $rules = [$this->formName => 'bail|mimes:' . $allowTypeListString . "|max:" . $allowSize * 1024];
        $messages = [
            'mimes' => __('attachment.error_extension', ['extension' => $allowTypeListString]),
            'max' => __('attachment.error_file_size', ['size' => $allowSize])
        ];
        $validator = Validator::make(['file' => $file], $rules, $messages);
        if ($validator->fails()) {
            if ($this->multiple) return false;
            $errors = $validator->errors()->getMessages();
            throw ValidationException::withMessages($errors);
        }
        return true;
    }


    /**
     * 上传文件到本地
     * @param $file
     * @return array
     */
    public function store($file)
    {
        $path = $this->newFileName && !$this->multiple ? $file->storeAs($this->uploadDir, $this->newFileName) : $file->store($this->uploadDir);

        // 图片打水印
        if ($this->uploadType == 'image' && isset($this->request->water) && $this->request->water) {
            $this->water($path);
        }
        return [
            'name' => $file->getClientOriginalName(),
            'ext' => $file->extension(),
            'size' => $file->getSize(),
            'path' => $path
        ];
    }

    /**
     * 文件入库
     * @param $file
     * @return array|false|string[]
     */
    private function save($file)
    {
        $result = [];
        DB::beginTransaction();
        $stored_file = $this->store($file);
        try {
            $attachment = Attachment::create($stored_file);
            $result = array_merge(['id' => $attachment->id], $stored_file, ['url' => Storage::url($stored_file['path'])]);
            DB::commit();
        } catch (FileException $e) {
            DB::rollback();
            Storage::delete($stored_file['path']);
        }
        return !empty($result) ? $result : false;
    }

    /**
     * 执行上传文件
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public function upload()
    {
        $file = $this->request->file($this->formName);
        if (is_array($file)) {
            throw new FileException(__('attachment.error_multiple_uploaded'));
        }
        if (!$this->request->hasFile($this->formName) || !$this->request->file($this->formName)->isValid()) {
            throw new FileException(__('attachment.invalid_file'));
        }
        $this->validateFile($file);
        $result = $this->save($file);
        if (!$result) throw new FileException(__('attachment.error_uploaded'));
        return [$result];
    }

    /**
     * 上传图片
     */
    public function uploadImage()
    {
        $this->uploadType = "image";
        return $this->upload();
    }

    /**
     * 上传音视频
     */
    public function uploadMedia()
    {
        $this->uploadType = "media";
        return $this->upload();
    }

    /**
     * 执行多文件上传
     * @param array $files
     * @return array
     * @throws ValidationException
     */
    public function multipleUpload()
    {
        $this->multiple = true;
        if (!$this->request->hasFile($this->formName)) {
            throw new FileException(__('attachment.invalid_file'));
        }
        $files = $this->request->file($this->formName);
        if (!is_array($files)) {
            throw new FileException(__('attachment.error_done'));
        }
        if (count($files) > $this->perLimit) {
            throw new FileException(__('attachment.error_per_limit', ['number' => $this->perLimit]));
        }
        $result = [];
        $errorTimes = 0;
        foreach ($files as $file) {
            if ($this->validateFile($file)) {
                $saveResult = $this->save($file);
                if ($saveResult) {
                    $result[] = $saveResult;
                }
            } else {
                $errorTimes++;
            }
        }
        if ($errorTimes == count($files) || empty($result)) {
            throw new FileException(__('attachment.error_uploaded'));
        }
        return $result;
    }

    /**
     * 获取文件远程访问地址
     * @param string $path
     * @return string
     */
    public static function fileUrl($path = "")
    {
        if (empty($path)) return "";
        try {
            $fileUrl = preg_match('/^http(s)?:\/\/.+/i', $path) ? $path : Storage::url($path);
        } catch (FileException $e) {
            $fileUrl = "";
        }
        return $fileUrl;
    }

    /**
     * 公开于接口的附件配置
     * @return array
     */
    public function config()
    {
        $allowTypeList = collect($this->allowTypeList)->map(function ($item) {
            return "." . $item;
        })->join(',');
        $allowImageTypeList = collect($this->allowImageTypeList)->map(function ($item) {
            return "." . $item;
        })->join(',');
        $allowMediaTypeList = collect($this->allowMediaTypeList)->map(function ($item) {
            return "." . $item;
        })->join(',');
        return [
            'allowTypeList' => $allowTypeList,
            'allowSize' => $this->getAllowSize(),
            'allowImageTypeList' => $allowImageTypeList,
            'allowImageSize' => $this->getAllowImageSize(),
            'allowMediaTypeList' => $allowMediaTypeList,
            'allowMediaSize' => $this->getAllowMediaSize()
        ];
    }

    /**
     * 将附件与关系表绑定，以便后期跟踪管理
     * @param $relationTableModel // 关联表模型实例
     * @param string $model_attachment_field // 关联表用于上传文件的字段
     * @param int $attachment_ids // 需绑定的附件表id，字符串或数组，如：1,2,3 或 [1,2,3]
     * @return bool|int
     */
    public static function bind($relationTableModel, $model_attachment_field = "", $attachment_ids = "" | [])
    {
        if (!$relationTableModel || empty($model_attachment_field)) {
            return false;
        }
        $model = get_class($relationTableModel);
        $model_table = $relationTableModel->getTable();
        $model_id = $relationTableModel->getKey();
//        Attachment::where('model', '=', $model)->where('model_id', '=', $model_id)->where('model_attachment_field', $model_attachment_field)->delete();
        if (empty($attachment_ids)) {
            return true;
        }
        $set_attachment_ids = is_array($attachment_ids) ? $attachment_ids : explode(',', $attachment_ids);
//        Attachment::withTrashed()->whereIn('id', $set_attachment_ids)->restore();
        $updateData = collect($set_attachment_ids)->map(function ($attachment_id) use ($relationTableModel, $model, $model_table, $model_id, $model_attachment_field) {
            return ['id' => $attachment_id, 'model' => $model, 'model_table' => $model_table, 'model_id' => $model_id, 'model_attachment_field' => $model_attachment_field];
        })->toArray();
        $attachment = new Attachment();
        return $attachment->batchUpdate($updateData);
    }

    /**
     * 图片预览列表
     * @param null $id
     * @return array
     */
    public static function previewList($id = null)
    {
        if (!$id) return [];
        $ids = is_string($id) ? explode(',', $id) : $id;
        $attachment = Attachment::whereIn('id', $ids)->get();
        $result = collect($attachment)->map(function ($item) {
            return [
                'id' => $item->id,
                'name' => $item->name,
                'description' => $item->description,
                'path' => $item->path,
                'url' => AttachmentService::fileUrl($item->path)
            ];
        });
        return $result->toArray();
    }

    /**
     * 获取有效图片id集
     * @param int $id
     * @return array
     */
    public static function getIds($id = '' | [])
    {
        if (empty($id)) return [];
        $ids = is_string($id) ? explode(',', $id) : $id;
        return Attachment::whereIn('id', $ids)->pluck('id')->toArray();
    }

    /**
     * 打水印
     * @param string $path // 图片地址
     * @return false
     */
    private function water($path = "")
    {
        if (!$path || !$this->isWatermark || !$this->watermarkImage || !$this->watermarkConditionWidth || !$this->watermarkConditionHeight) return false;
        $absoluteImagePath = Storage::path($path);
        $watermarkPosition = DictionaryService::getItemDescription('watermarkPositionVar', $this->watermarkPosition);
        if (!$watermarkPosition || !Storage::exists($path) || !Storage::exists($this->watermarkImage)) {
            return false;
        }
        [$width, $height] = @getimagesize($absoluteImagePath);
        if ($this->watermarkConditionWidth < $width && $this->watermarkConditionHeight < $height) {
            $waterAbsolutePath = Storage::path($this->watermarkImage);
            $image = Image::make($absoluteImagePath)->insert($waterAbsolutePath, $watermarkPosition, 10, 10);
            if ($this->watermarkPct > 0) {
                $image->opacity($this->watermarkPct);
            }
            $image->save($absoluteImagePath);
        }
    }


    /**
     * 获取图集请求数据，场景：图集数据保存
     * @param array $request_data // 接收前端的图集参数
     * @return array
     *
     * $request_data参数结构：[
     *  {id: 937, name: "1.png", description: "", path: '', url: ''}
     *  {id: 938, name: "2.png", description: "", path: '', url: ''}
     * ]
     *
     * 返回结构：[
     *  'image_list_ids' => '937,938'
     *  'image_list' => 'json字符串' //含id/name/description/path字段信息
     * ]
     */
    public static function getRequestPicture($request_data = [])
    {
        $image_list = "";
        $image_list_ids = [];
        if (is_array($request_data) && !empty($request_data)) {
            $image_list_ids = collect($request_data)->pluck('id')->toArray();
            $image_list_ids = self::getIds($image_list_ids);
            $image_list = collect($request_data)->map(function ($item) use ($image_list_ids) {
                if (isset($item['id']) && in_array($item['id'], $image_list_ids)) {
                    return [
                        'id' => $item['id'],
                        'name' => $item['name'] ?? "",
                        'description' => $item['description'] ?? "",
                        'path' => $item['path'] ?? ""
                    ];
                }
            })->toJson();
        }
        return ['image_list_ids' => join(',', $image_list_ids), 'image_list' => $image_list];
    }

    /**
     * 返回图集响应结果
     * @param string $jsonData // 图集字段的json数据
     * @return array
     * 返回结构：[
     *      [
     *          'id' => 标识,
     *          'name' => '图片名称'
     *          'description' => '描述',
     *          'path' => '存储路径',
     *          'url' => '远程访问地址',
     *          'image_url' => '远程访问地址'
     *      ],
     *      ......
     * ]
     */
    public static function getReponsePicture($jsonData = "")
    {
        if (!$jsonData) return [];
        $jsonData = trim($jsonData);
        $result = json_decode($jsonData, true);
        if (empty($result)) return [];
        foreach ($result as &$item) {
            $item['url'] = self::fileUrl($item['path']);
            $item['image_url'] = $item['url'];// image_url 与 url 一致，易于识别
        }
        return $result;
    }

    /**
     * 获取默认封面图访问地址
     * @return string
     */
    public static function getDefaultImageUrl() {
        return self::fileUrl('static/default.jpg');
    }
}
